Skip to content

fix(openid-connect): token introspection with client_secret_basic sends credentials in both header and body#13524

Closed
nic-6443 wants to merge 1 commit into
apache:masterfrom
nic-6443:fix/oidc-introspection-basic-auth-13085
Closed

fix(openid-connect): token introspection with client_secret_basic sends credentials in both header and body#13524
nic-6443 wants to merge 1 commit into
apache:masterfrom
nic-6443:fix/oidc-introspection-basic-auth-13085

Conversation

@nic-6443

Copy link
Copy Markdown
Member

Description

When introspection_endpoint_auth_method is client_secret_basic (the default), the token introspection request sends the client credentials twice: once in the Authorization: Basic header and once more as client_id/client_secret in the POST body. RFC 6749 Section 2.3.1 says the client must not use more than one authentication mechanism per request, so strict authorization servers reject the introspection call with invalid_client and every bearer-token request through the route fails with 401 — with the default plugin config.

The root cause is in lua-resty-openidc 1.8.0: introspect() unconditionally copies client_id/client_secret into the request body, and call_token_endpoint() then adds the Basic header for client_secret_basic without removing the body copies. There is an upstream issue (zmartzone/lua-resty-openidc#556), but it has been open for a while with no release containing a fix, so this patches it on the APISIX side without forking the library.

The fix extends the existing http_request_decorator hook usage in the plugin's introspection path: when the auth method is client_secret_basic, the decorator strips client_id/client_secret from the urlencoded request body before the request is sent. Scoping notes:

  • The decorator is set right before openidc.introspect() and cleared right after it returns (existing behavior), so it only affects the introspection call, not the token-endpoint calls of the authorization code flow.
  • Within openidc.introspect(), the decorator can also see a body-less discovery GET (when introspection_endpoint is not set explicitly), hence the req.body guard.
  • client_secret_post, private_key_jwt and client_secret_jwt are untouched: stripping only happens for client_secret_basic, where the credentials are already carried by the Basic header.

The new test file t/plugin/openid-connect11.t uses a mock introspection endpoint that behaves like a strict authorization server (rejects requests carrying credentials in both the header and the body) and covers client_secret_basic (default and explicit, with and without introspection_addon_headers) as well as client_secret_post, which must keep sending body credentials.

Which issue(s) this PR fixes:

Fixes #13085

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)

@kevinlzw

Copy link
Copy Markdown

Hi @nic-6443 ,

I have tried to fix the issue of upstream lua-resty-openidc in this PR. Can you check does it resolve your problem? Thank you.

@nic-6443

nic-6443 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@kevinlzw Thanks! I verified your fix against APISIX: I ran this PR's test suite (a strict mock introspection endpoint that rejects requests carrying credentials in both the Authorization header and the body) on current master with lua-resty-openidc bumped to 1.9.0 and without the plugin-side workaround from this PR — all tests pass. With 1.8.0 the same tests fail, so the upstream fix alone resolves the problem.

One detail worth noting: your fix keeps the legacy body-credentials behavior when introspection_endpoint_auth_method is unset, but that doesn't affect APISIX — the plugin schema declares default = "client_secret_basic", so the option is always materialized into the opts passed to openidc.introspect() and the new code path is taken.

Since v1.9.0 is already released and published to LuaRocks, the right fix for APISIX is now simply bumping the dependency in the rockspec instead of patching around it, so I'm closing this PR. Would you like to submit the version-bump PR? Since you authored the upstream fix, it seems fitting — and feel free to pick up t/plugin/openid-connect11.t from this PR as regression coverage if useful. Otherwise happy to do it myself.

@nic-6443 nic-6443 closed this Jul 3, 2026
@kevinlzw

kevinlzw commented Jul 3, 2026

Copy link
Copy Markdown

@nic-6443 Thanks for pointing this out. I have included this upgrade together with the related changes in #13649, so it should be covered there as part of the same update. Please feel free to take a look at that PR when you have a chance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: openid-connect token introspection client_secret_basic, secret sent in header and body

2 participants